package bancosys.tec.job.jms;

import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.NamingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import bancosys.tec.job.Job;
import bancosys.tec.job.JobDispatcher;
import bancosys.tec.job.JobDispatchingException;
import bancosys.tec.job.JobMessages;

/**
 * @created May 26, 2006
 * @author Gustavo Almeida
 */
public class QueueJobDispatcher implements JobDispatcher {
    /* injetadas */
    private String queueName = null;

    private String connectionfactory = null;

    private Context jndiContext = null;

    /* internas */
    private static Log log = LogFactory.getLog(QueueJobDispatcher.class);

    private QueueConnectionFactory queueConnectionFactory = null;

    private Queue queue = null;

    /**
     * Devolve a QueueConnectionFactory definida por <code>connectionfactory</code>.
     * 
     * @return a QueueConnectionFactory.
     * @throws NamingException quando o <code>connectionfactory</code> não está definido no contexto jndi.
     */
    private QueueConnectionFactory getQueueConnectionFactory() throws NamingException {
        if (this.queueConnectionFactory == null) {
            this.queueConnectionFactory = (QueueConnectionFactory) this.jndiContext.lookup(this.connectionfactory);
        }
        return this.queueConnectionFactory;
    }

    /**
     * Devolve a fila associada ao <code>queueName.</code>
     * 
     * @return a fila associada ao <code>queueName.</code>.
     * @throws NamingException quando o <code>queueName.</code> não está definido no contexto jndi.
     */
    private Queue getQueue() throws NamingException {
        if (this.queue == null) {
            this.queue = (Queue) this.jndiContext.lookup(this.queueName);
        }
        return this.queue;
    }

    /**
     * Envia o job para a fila associada.
     * 
     * @param job o job a ser enviado.
     * @throws JobDispatchingException quando não consegue enviar o job.
     */
    public void dispatch(Job job) throws JobDispatchingException {

        QueueConnectionFactory connectionFactory;
        Queue targetQueue;
        try {
            connectionFactory = this.getQueueConnectionFactory();
            targetQueue = this.getQueue();
        } catch (NamingException e) {
            throw new JobDispatchingException(JobMessages.ERROR_SENDING_TO_QUEUE.create(job.getId(), this.queueName), e);
        }

        QueueConnection queueConnection = null;
        try {
            queueConnection = connectionFactory.createQueueConnection();
            QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
            QueueSender queueSender = queueSession.createSender(targetQueue);
            ObjectMessage message = queueSession.createObjectMessage();
            message.setObject(job);
            queueSender.send(message);

        } catch (JMSException e) {
            throw new JobDispatchingException(JobMessages.ERROR_SENDING_TO_QUEUE.create(job.getId(), this.queueName), e);
        } finally {
            if (queueConnection != null) {
                try {
                    queueConnection.close();
                } catch (JMSException e) {
                    log.error("Unable to close JMS connection after sending job[" + job.getId() + "]", e);
                }
            }
        }
    }

    /**
     * Define o jndiContext.
     * 
     * @param jndiContext O jndiContext a ser definido.
     */
    public void setJndiContext(Context jndiContext) {
        this.jndiContext = jndiContext;
    }

    /**
     * Define o queueName.
     * 
     * @param queueName O queueName a ser definido.
     */
    public void setQueueName(String queueName) {
        this.queueName = queueName;
    }

    /**
     * Define o connectionfactory.
     * 
     * @param connectionfactory O connectionfactory a ser definido.
     */
    public void setConnectionfactory(String connectionfactory) {
        this.connectionfactory = connectionfactory;
    }

    /**
     * @return the queueName
     */
    public String getQueueName() {
        return this.queueName;
    }

    /**
     * @return the connectionfactory
     */
    public String getConnectionfactory() {
        return this.connectionfactory;
    }

    /**
     * @return the jndiContext
     */
    public Context getJndiContext() {
        return this.jndiContext;
    }

}
